This notebook explores the use of SingleR to perform cell-type annotation on datasets from the ScPCA project SCPCP000007 (Gawad lab data), which also features CITE-Seq ADT counts for validation.

Note: The first time you run this code it may take a few more minutes due to reference downloads, but they will be cached for faster future execution.

0.1 Set Up

# load the R project
project_root <- here::here()
renv::load(project_root)

suppressPackageStartupMessages({
  library(SingleCellExperiment)
  library(ggplot2)
})
theme_set(theme_bw())
set.seed(params$seed) # unclear if this is doing anything? probably not. but maybe later!

utils_dir <- file.path(project_root, "scripts", "utils")
source(file.path(utils_dir, "integration-helpers.R"))

sce_file_suffix <- "processed_citeseq.rds"

integrated_sce_file <- file.path(project_root, 
                                 params$integrated_sce_dir, 
                                 paste0(params$project_id, "_integrated_", params$integration_method, "_sce.rds")
                                 )
if (!file.exists(integrated_sce_file)){
  stop("Integrated SCE file could not be found.")
}

Read in the data:

library_metadata_df <- readr::read_tsv(file.path(project_root, params$library_file))
Rows: 25 Columns: 16
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (15): project_name, submitter, library_biomaterial_id, sample_biomateria...
lgl  (1): has_CITEseq

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
integrated_sce <- readr::read_rds(integrated_sce_file)

# Define the unintegrated SCE filenames
sce_file_paths <- library_metadata_df %>%
  dplyr::filter(project_name == params$project_id) %>%
  dplyr::mutate(sce_file_path = file.path(
    project_root, 
    integration_input_dir, 
    sample_biomaterial_id, 
    glue::glue("{library_biomaterial_id}_{sce_file_suffix}")
  )) %>%
  dplyr::pull(sce_file_path)

0.2 SingleR annotation

Here we perform celltype annotation with the given reference in params$reference on each of the Gawad libraries and look at their UMAPs colored by celltype.

First, set the reference:

if (params$reference == "hpca") {
  ref_data <- celldex::HumanPrimaryCellAtlasData(ensembl = TRUE)
} else if (params$reference == "blueprint_encode") {
  ref_data <- celldex::BlueprintEncodeData(ensembl = TRUE)
} else {
  stop("Bad reference parameter; either 'hpca' or 'blueprint_encode'.")
}
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
require("ensembldb")
Warning: Unable to map 1470 of 19363 requested IDs.

Define some functions:

annotate_SingleR <- function(sce, ref_data, label_name = "label.main") {
  # label_name is either "label.main" or "label.fine" to label with reference
  #  broad or fine-grained celltypes, respectively
  
  #label_name <- "label.fine"
  # return updated sce and the predictions themselves
  preds <- SingleR::SingleR(
    test = sce, 
    ref = ref_data,
    labels = colData(ref_data)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(params$cores)
  )
  
  return(preds)
}

plot_SingleR_heatmap <- function(SingleR_annotations) {
  # SingleR_annotations: result object from running SingleR::SingleR
  # Make and print a heatmap 
  
  heatmap <- SingleR::plotScoreHeatmap(SingleR_annotations, 
                            # this is default but let's be explicit
                            show.pruned = FALSE)
  
  print(heatmap)
}

plot_SingleR_UMAP <- function(sce, colname, legend_ncol = 1) {
  # sce: SCE object containing a column `colname` with celltype predictions
  # colname: Name of column with celltypes
  
  # Ensure this is a factor 
  colData(sce)[,colname] <- as.factor(colData(sce)[,colname])
  
  umap_df <- tibble::as_tibble(reducedDim(sce, "UMAP")) %>%
    dplyr::select(UMAP1 = V1, UMAP2 = V2) %>%
    dplyr::mutate(celltypes = colData(sce)[,colname])
  
  plot_colors <- rainbow( length(levels(umap_df$celltypes)) )
  
  umap <- ggplot(umap_df) + 
    aes(x = UMAP1, y = UMAP2, color = celltypes) +
    geom_point(size = 0.2, alpha = 0.5) + 
    scale_color_manual(values = plot_colors) +
    guides(color = guide_legend(override.aes = list(size=3),
                                ncol = legend_ncol)) +
    ggtitle(glue::glue("Library UMAP with projected SingleR celltype annotations"))
  
  print(umap)
  
}

# Function to run and optionally plot SingleR
# Return SCE with annotations `SingleR_annotations` and `SingleR_annotations_collapsed` columns
run_SingleR <- function(sce, 
                        max_celltypes = params$max_celltypes, 
                        viz = TRUE) {
  # Print out the library
  print(unique( metadata(sce)$library ))
  
  # Run SingleR
  SingleR_results <- annotate_SingleR(sce, ref_data)
  
  # Add annotations into sce as a frequency-ordered factor
  sce$SingleR_annotations <- forcats::fct_infreq(SingleR_results$pruned.labels)
   
  # Create an additional column `SingleR_annotations_collapsed` with only max_celltypes
  # This will automatically create an "Other" group with the remaining non-top-max_celltypes celltypes
  sce$SingleR_annotations_collapsed <- forcats::fct_lump_n(
    sce$SingleR_annotations, 
    max_celltypes
  )
  
  # plot if TRUE
  if (viz) {
    
    # Plot heatmap
    plot_SingleR_heatmap(SingleR_results)
    
    # Plot two UMAPs: with all celltypes and with max_celltypes
    plot_SingleR_UMAP(sce, "SingleR_annotations", legend_ncol=2)
    plot_SingleR_UMAP(sce, "SingleR_annotations_collapsed")
  }
  
  # Return the updated SCE object
  return(sce)
}

Here we go!

# Read in all SCE files
sce_list <- purrr::map(
  sce_file_paths, 
  readr::read_rds
)

# Annotate them all, popping out some viz along the way if specified
sce_list_annotated <- purrr::map(sce_list, run_SingleR, viz = TRUE)
[1] "SCPCL000295"
Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
Using compatibility `.name_repair`.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.

[1] "SCPCL000296"

[1] "SCPCL000297"

[1] "SCPCL000298"

[1] "SCPCL000299"

# Remove `ref_data` and `sce_list` which are large
rm(ref_data)
rm(sce_list)

0.3 UMAPs

This section has some UMAPs:

  • Celltype annotations from individual libraries applied to the integrated UMAP
  • ADT levels on the integrated UMAP for some proteins of interest: CD123 (leukemia marker) and CD3 (T-cell marker)
# First let's create a df of celltypes and ADTs:
extract_celltypes_adts <- function(sce) {
  # data frame of ADT counts
  adt_df <- logcounts(altExp(sce)) %>%
    as.matrix() %>%
    t() %>%
    tibble::as_tibble(rownames = "cell_barcode") %>%
    tidyr::pivot_longer(dplyr::starts_with("CD"),
                        names_to = "ADT", 
                        values_to = "logcounts")
  
  # Combine with data frame of annotations
  adt_df_anno <- tibble::tibble(
      celltype = sce$SingleR_annotations, 
      cell_barcode = rownames(colData(sce)),
      library = metadata(sce)$library
    ) %>%
    dplyr::inner_join(adt_df) %>%
    dplyr::mutate(cell_name = paste(cell_barcode, library, sep = "-"))
}

# This df is generally used below:
celltype_adt_df <- purrr::map_df(sce_list_annotated, extract_celltypes_adts) %>%
  # And join in UMAPs:
  dplyr::inner_join(
    reducedDim(integrated_sce, paste0(params$integration_method, "_UMAP")) %>%
      tibble::as_tibble(rownames = "cell_name") %>%
      dplyr::rename(UMAP1 = V1, UMAP2 = V2)
  ) %>%
  dplyr::select(-cell_barcode) %>%
  # remake `celltype_collapsed` so that it's the top params$max_celltypes 
  #  considering all libraries pooled
  dplyr::mutate(celltype_collapsed = forcats::fct_lump(celltype, params$max_celltypes))
Joining, by = "cell_barcode"
Joining, by = "cell_barcode"
Joining, by = "cell_barcode"
Joining, by = "cell_barcode"
Joining, by = "cell_barcode"
Joining, by = "cell_name"
# We can now remove the large `sce_list_annotated` and `integrated_sce`
rm(sce_list_annotated)
rm(integrated_sce)

# See df:
celltype_adt_df
# RNA UMAP:
all_celltypes_rna_umap <- celltype_adt_df %>%
  dplyr::select(UMAP1, UMAP2, celltype) %>%
  dplyr::distinct() %>%
  ggplot() +
  aes(x = UMAP1, y = UMAP2, color = celltype) + 
  geom_point(size = 0.3, alpha = 0.4) + 
  guides(color = guide_legend(override.aes = list(size=3))) +
  ggtitle(glue::glue("Integrated UMAP with all SingleR-annotated celltypes shown"))

all_celltypes_rna_umap

all_celltypes_rna_umap +
  lims(
    x = c(-6, 4), 
    y = c(-5, 5.5)
  ) +
  ggtitle("The above UMAP, zoomed in")
Warning: Removed 487 rows containing missing values (geom_point).

# RNA UMAP:
max_celltypes_rna_umap <- celltype_adt_df %>%
  dplyr::select(UMAP1, UMAP2, celltype_collapsed) %>%
  dplyr::distinct() %>%
  ggplot() +
  aes(x = UMAP1, y = UMAP2, color = celltype_collapsed) + 
  geom_point(size = 0.3, alpha = 0.4) + 
  guides(color = guide_legend(override.aes = list(size=3))) +
  labs(
    title = glue::glue("Integrated UMAP with top {params$max_celltypes} SingleR-annotated celltypes shown"), 
    color = glue::glue("Top {params$max_celltypes} celltypes"))

max_celltypes_rna_umap

max_celltypes_rna_umap +
  lims(
    x = c(-6, 4), 
    y = c(-5, 5.5)
  ) +
  ggtitle("The above UMAP, zoomed in")
Warning: Removed 487 rows containing missing values (geom_point).

# function to make UMAPs colored by ADT
make_adt_umap <- function(celltype_adt_df, adt_name) {

  celltype_adt_df %>%
    dplyr::filter(ADT == adt_name) %>%
    ggplot() + 
    aes(x = UMAP1, y = UMAP2, color = logcounts) + 
    geom_point(size = 0.3, alpha = 0.4) +
    scale_color_viridis_c() + 
    labs(
      title = glue::glue("{adt_name} expression"),
      color = "Normalized expression")
}

make_adt_umap(celltype_adt_df, "CD3") # bottom left corner tiny dots!

make_adt_umap(celltype_adt_df, "CD123") # everything is tumor

make_adt_umap(celltype_adt_df, "CD45") # should be on all differentiated cells more or less\

make_adt_umap(celltype_adt_df, "CD19") # b cells

0.4 ADT distributions

This section explores ADT counts across predicted celltypes.

adt_celltype_violin <- function(df, celltype_column, adt_name) {
  
  # colors, using dplyr since tidyeval land
  fill_vector <- df %>%
    dplyr::pull({{celltype_column}}) %>%
    unique() %>%
    length() %>%
    rainbow()

  celltype_adt_df %>%
    dplyr::filter(ADT == adt_name) %>%
    dplyr::mutate(celltype_plot_column = forcats::fct_reorder({{celltype_column}}, logcounts)) %>%
    ggplot() + 
    aes(x = celltype_plot_column,
        y = logcounts, 
        fill = celltype_plot_column) + 
    geom_violin(alpha = 0.7) + 
    stat_summary() + 
    scale_fill_manual(values = fill_vector) +
    labs(
      title = glue::glue("{adt_name} normalized expression across celltypes"), 
      x = "Celltypes", 
      y = "Normalized expression"
    ) + 
    theme(legend.position = "none", 
          axis.text.x = element_text(angle = 30, 
                                     hjust = 1, 
                                     size = rel(0.8)))

}

## CD3
adt_celltype_violin(celltype_adt_df, celltype, "CD3") 
Warning: Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
No summary function supplied, defaulting to `mean_se()`
Warning: Removed 4 rows containing missing values (geom_segment).

adt_celltype_violin(celltype_adt_df, celltype_collapsed, "CD3")
No summary function supplied, defaulting to `mean_se()`

## CD19
adt_celltype_violin(celltype_adt_df, celltype, "CD19") 
Warning: Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
No summary function supplied, defaulting to `mean_se()`
Warning: Removed 4 rows containing missing values (geom_segment).

adt_celltype_violin(celltype_adt_df, celltype_collapsed, "CD19")
No summary function supplied, defaulting to `mean_se()`

## CD123
adt_celltype_violin(celltype_adt_df, celltype, "CD123") 
Warning: Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
Groups with fewer than two data points have been dropped.
No summary function supplied, defaulting to `mean_se()`
Warning: Removed 4 rows containing missing values (geom_segment).

adt_celltype_violin(celltype_adt_df, celltype_collapsed, "CD123")
No summary function supplied, defaulting to `mean_se()`

0.5 Comparing references

To build a sense of how different references perform, let’s just look at one SCE (arbitrarily chosen as the first):

sce <- readr::read_rds(sce_file_paths[[1]])

Functions for comparing references:

# Return a tibble of predictions from the two references

# Define outside of function to avoid rerunning over and over and over AND OVER.
hpca_ref <- celldex::HumanPrimaryCellAtlasData(ensembl = TRUE)
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
Warning: Unable to map 1470 of 19363 requested IDs.
blueprint_encode_ref <- celldex::BlueprintEncodeData(ensembl = TRUE)
snapshotDate(): 2022-04-26
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
see ?celldex and browseVignettes('celldex') for documentation
loading from cache
snapshotDate(): 2022-04-25
loading from cache
Warning: Unable to map 532 of 19859 requested IDs.
compare_predictions <- function(sce, 
                                label_name) {
  
  # HPCA prediction
  preds_hpca <- SingleR::SingleR(
    test = sce, 
    ref = hpca_ref,
    labels = colData(hpca_ref)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(params$cores)
  )
  
  # Blueprint/ENCODE prediction
  preds_be <- SingleR::SingleR(
    test = sce, 
    ref = blueprint_encode_ref,
    labels = colData(blueprint_encode_ref)[,label_name], 
    BPPARAM = BiocParallel::MulticoreParam(params$cores)
  )
  
  # Combine results into a wide tibble:
  preds_df <- tibble::tibble(
    hpca = preds_hpca$labels, 
    cell_barcode = rownames(preds_hpca)) %>%
    dplyr::inner_join(
      tibble::tibble(
        blueprint_encode = preds_be$labels, 
        cell_barcode = rownames(preds_be)) 
    )

  return(preds_df)
}

The celldex package contains bulk RNA-Seq datasets for use as reference. These two are likely most useful:

The HPCA reference consists of publicly available microarray datasets derived from human primary cells (Mabbott et al. 2013). Most of the labels refer to blood subpopulations but cell types from other tissues are also available.

The Blueprint/ENCODE reference consists of bulk RNA-seq data for pure stroma and immune cells generated by Blueprint (Martens and Stunnenberg 2013) and ENCODE projects (The ENCODE Project Consortium 2012).

main_labels_df <- compare_predictions(sce, "label.main")
Joining, by = "cell_barcode"
fine_labels_df <- compare_predictions(sce, "label.fine")
Joining, by = "cell_barcode"
# remove refs which are large
rm(hpca_ref)
rm(blueprint_encode_ref)

How do these predictions compare? Note that these references often use different labels for the same cell type so we shouldn’t expect a perfect diagonal below:

#function for making comparison heatmaps
make_comparison_heatmap <- function(df) {
  df %>%
    dplyr::mutate(
      hpca = forcats::fct_rev(forcats::fct_infreq(hpca)),
      blueprint_encode = forcats::fct_rev(forcats::fct_infreq(blueprint_encode))
    ) %>%
    dplyr::count(hpca, blueprint_encode) %>%
  ggplot() + 
  aes(x = blueprint_encode, y = hpca, fill = n) + 
  geom_tile(color = "black") + 
  geom_text(aes(label = n), size = rel(1.2)) + 
  scale_fill_distiller(palette = "RdYlBu", name = "Num. cells") + 
  # background grid from theme_bw does not match up with rectangles.
  theme_classic() +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))
}
make_comparison_heatmap(main_labels_df) + ggtitle("Broad label comparison")

make_comparison_heatmap(fine_labels_df) + ggtitle("Fine label comparison")

0.6 Session Info

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur ... 10.16

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] ensembldb_2.20.2            AnnotationFilter_1.20.0    
 [3] GenomicFeatures_1.48.3      AnnotationDbi_1.58.0       
 [5] celldex_1.6.0               magrittr_2.0.3             
 [7] ggplot2_3.3.6               SingleCellExperiment_1.18.0
 [9] SummarizedExperiment_1.26.1 Biobase_2.56.0             
[11] GenomicRanges_1.48.0        GenomeInfoDb_1.32.3        
[13] IRanges_2.30.0              S4Vectors_0.34.0           
[15] BiocGenerics_0.42.0         MatrixGenerics_1.8.1       
[17] matrixStats_0.62.0         

loaded via a namespace (and not attached):
  [1] AnnotationHub_3.4.0           BiocFileCache_2.4.0          
  [3] lazyeval_0.2.2                splines_4.2.1                
  [5] BiocParallel_1.30.3           digest_0.6.30                
  [7] htmltools_0.5.3               viridis_0.6.2                
  [9] fansi_1.0.3                   memoise_2.0.1                
 [11] ScaledMatrix_1.4.0            tzdb_0.3.0                   
 [13] Biostrings_2.64.0             miQC_1.4.0                   
 [15] readr_2.1.2                   vroom_1.5.7                  
 [17] prettyunits_1.1.1             colorspace_2.0-3             
 [19] blob_1.2.3                    rappdirs_0.3.3               
 [21] xfun_0.34                     dplyr_1.0.9                  
 [23] crayon_1.5.1                  RCurl_1.98-1.8               
 [25] jsonlite_1.8.3                glue_1.6.2                   
 [27] gtable_0.3.0                  zlibbioc_1.42.0              
 [29] XVector_0.36.0                DelayedArray_0.22.0          
 [31] BiocSingular_1.12.0           scales_1.2.0                 
 [33] pheatmap_1.0.12               DBI_1.1.3                    
 [35] Rcpp_1.0.9                    viridisLite_0.4.0            
 [37] xtable_1.8-4                  progress_1.2.2               
 [39] bit_4.0.4                     rsvd_1.0.5                   
 [41] httr_1.4.3                    RColorBrewer_1.1-3           
 [43] modeltools_0.2-23             ellipsis_0.3.2               
 [45] pkgconfig_2.0.3               XML_3.99-0.10                
 [47] flexmix_2.3-18                farver_2.1.1                 
 [49] nnet_7.3-17                   sass_0.4.2                   
 [51] dbplyr_2.2.1                  utf8_1.2.2                   
 [53] here_1.0.1                    tidyselect_1.1.2             
 [55] labeling_0.4.2                rlang_1.0.6                  
 [57] later_1.3.0                   munsell_0.5.0                
 [59] BiocVersion_3.15.2            tools_4.2.1                  
 [61] cachem_1.0.6                  cli_3.4.1                    
 [63] generics_0.1.3                RSQLite_2.2.15               
 [65] ExperimentHub_2.4.0           evaluate_0.18                
 [67] stringr_1.4.1                 fastmap_1.1.0                
 [69] yaml_2.3.6                    knitr_1.40                   
 [71] bit64_4.0.5                   purrr_0.3.4                  
 [73] KEGGREST_1.36.3               sparseMatrixStats_1.8.0      
 [75] mime_0.12                     xml2_1.3.3                   
 [77] biomaRt_2.52.0                compiler_4.2.1               
 [79] filelock_1.0.2                curl_4.3.2                   
 [81] png_0.1-7                     interactiveDisplayBase_1.34.0
 [83] tibble_3.1.8                  bslib_0.4.1                  
 [85] stringi_1.7.8                 highr_0.9                    
 [87] forcats_0.5.2                 lattice_0.20-45              
 [89] ProtGenerics_1.28.0           Matrix_1.4-1                 
 [91] vctrs_0.4.1                   pillar_1.8.0                 
 [93] lifecycle_1.0.1               BiocManager_1.30.18          
 [95] jquerylib_0.1.4               BiocNeighbors_1.14.0         
 [97] bitops_1.0-7                  irlba_2.3.5                  
 [99] httpuv_1.6.5                  rtracklayer_1.56.1           
[101] R6_2.5.1                      BiocIO_1.6.0                 
[103] promises_1.2.0.1              renv_0.16.0                  
[105] gridExtra_2.3                 SingleR_1.10.0               
[107] codetools_0.2-18              assertthat_0.2.1             
[109] rprojroot_2.0.3               rjson_0.2.21                 
[111] withr_2.5.0                   GenomicAlignments_1.32.1     
[113] Rsamtools_2.12.0              GenomeInfoDbData_1.2.8       
[115] parallel_4.2.1                hms_1.1.1                    
[117] grid_4.2.1                    beachmat_2.12.0              
[119] tidyr_1.2.0                   rmarkdown_2.18               
[121] DelayedMatrixStats_1.18.0     shiny_1.7.2                  
[123] restfulr_0.0.15              
LS0tCnBhcmFtczoKICBzZWVkOiAyMDIyCiAgbGlicmFyeV9maWxlOiAhciBmaWxlLnBhdGgoInNhbXBsZS1pbmZvIiwgInNjcGNhLXByb2Nlc3NlZC1saWJyYXJpZXMudHN2IikKICBwcm9qZWN0X2lkOiAiU0NQQ1AwMDAwMDciCiAgcmVmZXJlbmNlOiAiaHBjYSIKICBjb3JlczogNAogIGludGVncmF0ZWRfc2NlX2RpcjogIXIgZmlsZS5wYXRoKCJyZXN1bHRzIiwgInNjcGNhIiwgImludGVncmF0ZWRfc2NlIikKICBpbnRlZ3JhdGlvbl9tZXRob2Q6ICJmYXN0bW5uIgogIG1heF9jZWxsdHlwZXM6IDUKdGl0bGU6ICJDZWxsIHR5cGUgYW5ub3RhdGlvbiBleHBsb3JhdGlvbiIKYXV0aG9yOiAiRGF0YSBMYWIiCmRhdGU6ICJgciBwYXJhbXMkZGF0ZWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQoKVGhpcyBub3RlYm9vayBleHBsb3JlcyB0aGUgdXNlIG9mIFtgU2luZ2xlUmBdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy9yZWxlYXNlL1NpbmdsZVJCb29rLykgdG8gcGVyZm9ybSBjZWxsLXR5cGUgYW5ub3RhdGlvbiBvbiBkYXRhc2V0cyBmcm9tIHRoZSBTY1BDQSBwcm9qZWN0IFNDUENQMDAwMDA3IChHYXdhZCBsYWIgZGF0YSksIHdoaWNoIGFsc28gZmVhdHVyZXMgQ0lURS1TZXEgQURUIGNvdW50cyBmb3IgdmFsaWRhdGlvbi4KCioqTm90ZToqKiBUaGUgZmlyc3QgdGltZSB5b3UgcnVuIHRoaXMgY29kZSBpdCBtYXkgdGFrZSBhIGZldyBtb3JlIG1pbnV0ZXMgZHVlIHRvIHJlZmVyZW5jZSBkb3dubG9hZHMsIGJ1dCB0aGV5IHdpbGwgYmUgY2FjaGVkIGZvciBmYXN0ZXIgZnV0dXJlIGV4ZWN1dGlvbi4KCgojIyBTZXQgVXAKCmBgYHtyIHNldHVwfQojIGxvYWQgdGhlIFIgcHJvamVjdApwcm9qZWN0X3Jvb3QgPC0gaGVyZTo6aGVyZSgpCnJlbnY6OmxvYWQocHJvamVjdF9yb290KQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoZ2dwbG90MikKfSkKdGhlbWVfc2V0KHRoZW1lX2J3KCkpCnNldC5zZWVkKHBhcmFtcyRzZWVkKSAjIHVuY2xlYXIgaWYgdGhpcyBpcyBkb2luZyBhbnl0aGluZz8gcHJvYmFibHkgbm90LiBidXQgbWF5YmUgbGF0ZXIhCgp1dGlsc19kaXIgPC0gZmlsZS5wYXRoKHByb2plY3Rfcm9vdCwgInNjcmlwdHMiLCAidXRpbHMiKQpzb3VyY2UoZmlsZS5wYXRoKHV0aWxzX2RpciwgImludGVncmF0aW9uLWhlbHBlcnMuUiIpKQoKc2NlX2ZpbGVfc3VmZml4IDwtICJwcm9jZXNzZWRfY2l0ZXNlcS5yZHMiCgppbnRlZ3JhdGVkX3NjZV9maWxlIDwtIGZpbGUucGF0aChwcm9qZWN0X3Jvb3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbXMkaW50ZWdyYXRlZF9zY2VfZGlyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKHBhcmFtcyRwcm9qZWN0X2lkLCAiX2ludGVncmF0ZWRfIiwgcGFyYW1zJGludGVncmF0aW9uX21ldGhvZCwgIl9zY2UucmRzIikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQppZiAoIWZpbGUuZXhpc3RzKGludGVncmF0ZWRfc2NlX2ZpbGUpKXsKICBzdG9wKCJJbnRlZ3JhdGVkIFNDRSBmaWxlIGNvdWxkIG5vdCBiZSBmb3VuZC4iKQp9CmBgYAoKUmVhZCBpbiB0aGUgZGF0YToKCmBgYHtyfQpsaWJyYXJ5X21ldGFkYXRhX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihmaWxlLnBhdGgocHJvamVjdF9yb290LCBwYXJhbXMkbGlicmFyeV9maWxlKSkKCmludGVncmF0ZWRfc2NlIDwtIHJlYWRyOjpyZWFkX3JkcyhpbnRlZ3JhdGVkX3NjZV9maWxlKQoKIyBEZWZpbmUgdGhlIHVuaW50ZWdyYXRlZCBTQ0UgZmlsZW5hbWVzCnNjZV9maWxlX3BhdGhzIDwtIGxpYnJhcnlfbWV0YWRhdGFfZGYgJT4lCiAgZHBseXI6OmZpbHRlcihwcm9qZWN0X25hbWUgPT0gcGFyYW1zJHByb2plY3RfaWQpICU+JQogIGRwbHlyOjptdXRhdGUoc2NlX2ZpbGVfcGF0aCA9IGZpbGUucGF0aCgKICAgIHByb2plY3Rfcm9vdCwgCiAgICBpbnRlZ3JhdGlvbl9pbnB1dF9kaXIsIAogICAgc2FtcGxlX2Jpb21hdGVyaWFsX2lkLCAKICAgIGdsdWU6OmdsdWUoIntsaWJyYXJ5X2Jpb21hdGVyaWFsX2lkfV97c2NlX2ZpbGVfc3VmZml4fSIpCiAgKSkgJT4lCiAgZHBseXI6OnB1bGwoc2NlX2ZpbGVfcGF0aCkKYGBgCgoKIyMgYFNpbmdsZVJgIGFubm90YXRpb24KCkhlcmUgd2UgcGVyZm9ybSBjZWxsdHlwZSBhbm5vdGF0aW9uIHdpdGggdGhlIGdpdmVuIHJlZmVyZW5jZSBpbiBgcGFyYW1zJHJlZmVyZW5jZWAgb24gZWFjaCBvZiB0aGUgR2F3YWQgbGlicmFyaWVzIGFuZCBsb29rIGF0IHRoZWlyIFVNQVBzIGNvbG9yZWQgYnkgY2VsbHR5cGUuCgpGaXJzdCwgc2V0IHRoZSByZWZlcmVuY2U6CmBgYHtyfQppZiAocGFyYW1zJHJlZmVyZW5jZSA9PSAiaHBjYSIpIHsKICByZWZfZGF0YSA8LSBjZWxsZGV4OjpIdW1hblByaW1hcnlDZWxsQXRsYXNEYXRhKGVuc2VtYmwgPSBUUlVFKQp9IGVsc2UgaWYgKHBhcmFtcyRyZWZlcmVuY2UgPT0gImJsdWVwcmludF9lbmNvZGUiKSB7CiAgcmVmX2RhdGEgPC0gY2VsbGRleDo6Qmx1ZXByaW50RW5jb2RlRGF0YShlbnNlbWJsID0gVFJVRSkKfSBlbHNlIHsKICBzdG9wKCJCYWQgcmVmZXJlbmNlIHBhcmFtZXRlcjsgZWl0aGVyICdocGNhJyBvciAnYmx1ZXByaW50X2VuY29kZScuIikKfQpgYGAKCkRlZmluZSBzb21lIGZ1bmN0aW9uczoKYGBge3J9CmFubm90YXRlX1NpbmdsZVIgPC0gZnVuY3Rpb24oc2NlLCByZWZfZGF0YSwgbGFiZWxfbmFtZSA9ICJsYWJlbC5tYWluIikgewogICMgbGFiZWxfbmFtZSBpcyBlaXRoZXIgImxhYmVsLm1haW4iIG9yICJsYWJlbC5maW5lIiB0byBsYWJlbCB3aXRoIHJlZmVyZW5jZQogICMgIGJyb2FkIG9yIGZpbmUtZ3JhaW5lZCBjZWxsdHlwZXMsIHJlc3BlY3RpdmVseQogIAogICNsYWJlbF9uYW1lIDwtICJsYWJlbC5maW5lIgogICMgcmV0dXJuIHVwZGF0ZWQgc2NlIGFuZCB0aGUgcHJlZGljdGlvbnMgdGhlbXNlbHZlcwogIHByZWRzIDwtIFNpbmdsZVI6OlNpbmdsZVIoCiAgICB0ZXN0ID0gc2NlLCAKICAgIHJlZiA9IHJlZl9kYXRhLAogICAgbGFiZWxzID0gY29sRGF0YShyZWZfZGF0YSlbLGxhYmVsX25hbWVdLCAKICAgIEJQUEFSQU0gPSBCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKHBhcmFtcyRjb3JlcykKICApCiAgCiAgcmV0dXJuKHByZWRzKQp9CgpwbG90X1NpbmdsZVJfaGVhdG1hcCA8LSBmdW5jdGlvbihTaW5nbGVSX2Fubm90YXRpb25zKSB7CiAgIyBTaW5nbGVSX2Fubm90YXRpb25zOiByZXN1bHQgb2JqZWN0IGZyb20gcnVubmluZyBTaW5nbGVSOjpTaW5nbGVSCiAgIyBNYWtlIGFuZCBwcmludCBhIGhlYXRtYXAgCiAgCiAgaGVhdG1hcCA8LSBTaW5nbGVSOjpwbG90U2NvcmVIZWF0bWFwKFNpbmdsZVJfYW5ub3RhdGlvbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGlzIGlzIGRlZmF1bHQgYnV0IGxldCdzIGJlIGV4cGxpY2l0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93LnBydW5lZCA9IEZBTFNFKQogIAogIHByaW50KGhlYXRtYXApCn0KCnBsb3RfU2luZ2xlUl9VTUFQIDwtIGZ1bmN0aW9uKHNjZSwgY29sbmFtZSwgbGVnZW5kX25jb2wgPSAxKSB7CiAgIyBzY2U6IFNDRSBvYmplY3QgY29udGFpbmluZyBhIGNvbHVtbiBgY29sbmFtZWAgd2l0aCBjZWxsdHlwZSBwcmVkaWN0aW9ucwogICMgY29sbmFtZTogTmFtZSBvZiBjb2x1bW4gd2l0aCBjZWxsdHlwZXMKICAKICAjIEVuc3VyZSB0aGlzIGlzIGEgZmFjdG9yIAogIGNvbERhdGEoc2NlKVssY29sbmFtZV0gPC0gYXMuZmFjdG9yKGNvbERhdGEoc2NlKVssY29sbmFtZV0pCiAgCiAgdW1hcF9kZiA8LSB0aWJibGU6OmFzX3RpYmJsZShyZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFVNQVAxID0gVjEsIFVNQVAyID0gVjIpICU+JQogICAgZHBseXI6Om11dGF0ZShjZWxsdHlwZXMgPSBjb2xEYXRhKHNjZSlbLGNvbG5hbWVdKQogIAogIHBsb3RfY29sb3JzIDwtIHJhaW5ib3coIGxlbmd0aChsZXZlbHModW1hcF9kZiRjZWxsdHlwZXMpKSApCiAgCiAgdW1hcCA8LSBnZ3Bsb3QodW1hcF9kZikgKyAKICAgIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjZWxsdHlwZXMpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuMiwgYWxwaGEgPSAwLjUpICsgCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGxvdF9jb2xvcnMpICsKICAgIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemU9MyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGxlZ2VuZF9uY29sKSkgKwogICAgZ2d0aXRsZShnbHVlOjpnbHVlKCJMaWJyYXJ5IFVNQVAgd2l0aCBwcm9qZWN0ZWQgU2luZ2xlUiBjZWxsdHlwZSBhbm5vdGF0aW9ucyIpKQogIAogIHByaW50KHVtYXApCiAgCn0KCiMgRnVuY3Rpb24gdG8gcnVuIGFuZCBvcHRpb25hbGx5IHBsb3QgU2luZ2xlUgojIFJldHVybiBTQ0Ugd2l0aCBhbm5vdGF0aW9ucyBgU2luZ2xlUl9hbm5vdGF0aW9uc2AgYW5kIGBTaW5nbGVSX2Fubm90YXRpb25zX2NvbGxhcHNlZGAgY29sdW1ucwpydW5fU2luZ2xlUiA8LSBmdW5jdGlvbihzY2UsIAogICAgICAgICAgICAgICAgICAgICAgICBtYXhfY2VsbHR5cGVzID0gcGFyYW1zJG1heF9jZWxsdHlwZXMsIAogICAgICAgICAgICAgICAgICAgICAgICB2aXogPSBUUlVFKSB7CiAgIyBQcmludCBvdXQgdGhlIGxpYnJhcnkKICBwcmludCh1bmlxdWUoIG1ldGFkYXRhKHNjZSkkbGlicmFyeSApKQogIAogICMgUnVuIFNpbmdsZVIKICBTaW5nbGVSX3Jlc3VsdHMgPC0gYW5ub3RhdGVfU2luZ2xlUihzY2UsIHJlZl9kYXRhKQogIAogICMgQWRkIGFubm90YXRpb25zIGludG8gc2NlIGFzIGEgZnJlcXVlbmN5LW9yZGVyZWQgZmFjdG9yCiAgc2NlJFNpbmdsZVJfYW5ub3RhdGlvbnMgPC0gZm9yY2F0czo6ZmN0X2luZnJlcShTaW5nbGVSX3Jlc3VsdHMkcHJ1bmVkLmxhYmVscykKICAgCiAgIyBDcmVhdGUgYW4gYWRkaXRpb25hbCBjb2x1bW4gYFNpbmdsZVJfYW5ub3RhdGlvbnNfY29sbGFwc2VkYCB3aXRoIG9ubHkgbWF4X2NlbGx0eXBlcwogICMgVGhpcyB3aWxsIGF1dG9tYXRpY2FsbHkgY3JlYXRlIGFuICJPdGhlciIgZ3JvdXAgd2l0aCB0aGUgcmVtYWluaW5nIG5vbi10b3AtbWF4X2NlbGx0eXBlcyBjZWxsdHlwZXMKICBzY2UkU2luZ2xlUl9hbm5vdGF0aW9uc19jb2xsYXBzZWQgPC0gZm9yY2F0czo6ZmN0X2x1bXBfbigKICAgIHNjZSRTaW5nbGVSX2Fubm90YXRpb25zLCAKICAgIG1heF9jZWxsdHlwZXMKICApCiAgCiAgIyBwbG90IGlmIFRSVUUKICBpZiAodml6KSB7CiAgICAKICAgICMgUGxvdCBoZWF0bWFwCiAgICBwbG90X1NpbmdsZVJfaGVhdG1hcChTaW5nbGVSX3Jlc3VsdHMpCiAgICAKICAgICMgUGxvdCB0d28gVU1BUHM6IHdpdGggYWxsIGNlbGx0eXBlcyBhbmQgd2l0aCBtYXhfY2VsbHR5cGVzCiAgICBwbG90X1NpbmdsZVJfVU1BUChzY2UsICJTaW5nbGVSX2Fubm90YXRpb25zIiwgbGVnZW5kX25jb2w9MikKICAgIHBsb3RfU2luZ2xlUl9VTUFQKHNjZSwgIlNpbmdsZVJfYW5ub3RhdGlvbnNfY29sbGFwc2VkIikKICB9CiAgCiAgIyBSZXR1cm4gdGhlIHVwZGF0ZWQgU0NFIG9iamVjdAogIHJldHVybihzY2UpCn0KYGBgCgpIZXJlIHdlIGdvIQoKYGBge3J9CiMgUmVhZCBpbiBhbGwgU0NFIGZpbGVzCnNjZV9saXN0IDwtIHB1cnJyOjptYXAoCiAgc2NlX2ZpbGVfcGF0aHMsIAogIHJlYWRyOjpyZWFkX3JkcwopCgojIEFubm90YXRlIHRoZW0gYWxsLCBwb3BwaW5nIG91dCBzb21lIHZpeiBhbG9uZyB0aGUgd2F5IGlmIHNwZWNpZmllZApzY2VfbGlzdF9hbm5vdGF0ZWQgPC0gcHVycnI6Om1hcChzY2VfbGlzdCwgcnVuX1NpbmdsZVIsIHZpeiA9IFRSVUUpCgojIFJlbW92ZSBgcmVmX2RhdGFgIGFuZCBgc2NlX2xpc3RgIHdoaWNoIGFyZSBsYXJnZQpybShyZWZfZGF0YSkKcm0oc2NlX2xpc3QpCmBgYAoKIyMgVU1BUHMKClRoaXMgc2VjdGlvbiBoYXMgc29tZSBVTUFQczoKCi0gQ2VsbHR5cGUgYW5ub3RhdGlvbnMgZnJvbSBpbmRpdmlkdWFsIGxpYnJhcmllcyBhcHBsaWVkIHRvIHRoZSBpbnRlZ3JhdGVkIFVNQVAKLSBBRFQgbGV2ZWxzIG9uIHRoZSBpbnRlZ3JhdGVkIFVNQVAgZm9yIHNvbWUgcHJvdGVpbnMgb2YgaW50ZXJlc3Q6IGBDRDEyM2AgKGxldWtlbWlhIG1hcmtlcikgYW5kIGBDRDNgIChULWNlbGwgbWFya2VyKQoKCmBgYHtyfQojIEZpcnN0IGxldCdzIGNyZWF0ZSBhIGRmIG9mIGNlbGx0eXBlcyBhbmQgQURUczoKZXh0cmFjdF9jZWxsdHlwZXNfYWR0cyA8LSBmdW5jdGlvbihzY2UpIHsKICAjIGRhdGEgZnJhbWUgb2YgQURUIGNvdW50cwogIGFkdF9kZiA8LSBsb2djb3VudHMoYWx0RXhwKHNjZSkpICU+JQogICAgYXMubWF0cml4KCkgJT4lCiAgICB0KCkgJT4lCiAgICB0aWJibGU6OmFzX3RpYmJsZShyb3duYW1lcyA9ICJjZWxsX2JhcmNvZGUiKSAlPiUKICAgIHRpZHlyOjpwaXZvdF9sb25nZXIoZHBseXI6OnN0YXJ0c193aXRoKCJDRCIpLAogICAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJBRFQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gImxvZ2NvdW50cyIpCiAgCiAgIyBDb21iaW5lIHdpdGggZGF0YSBmcmFtZSBvZiBhbm5vdGF0aW9ucwogIGFkdF9kZl9hbm5vIDwtIHRpYmJsZTo6dGliYmxlKAogICAgICBjZWxsdHlwZSA9IHNjZSRTaW5nbGVSX2Fubm90YXRpb25zLCAKICAgICAgY2VsbF9iYXJjb2RlID0gcm93bmFtZXMoY29sRGF0YShzY2UpKSwKICAgICAgbGlicmFyeSA9IG1ldGFkYXRhKHNjZSkkbGlicmFyeQogICAgKSAlPiUKICAgIGRwbHlyOjppbm5lcl9qb2luKGFkdF9kZikgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGNlbGxfbmFtZSA9IHBhc3RlKGNlbGxfYmFyY29kZSwgbGlicmFyeSwgc2VwID0gIi0iKSkKfQoKIyBUaGlzIGRmIGlzIGdlbmVyYWxseSB1c2VkIGJlbG93OgpjZWxsdHlwZV9hZHRfZGYgPC0gcHVycnI6Om1hcF9kZihzY2VfbGlzdF9hbm5vdGF0ZWQsIGV4dHJhY3RfY2VsbHR5cGVzX2FkdHMpICU+JQogICMgQW5kIGpvaW4gaW4gVU1BUHM6CiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICByZWR1Y2VkRGltKGludGVncmF0ZWRfc2NlLCBwYXN0ZTAocGFyYW1zJGludGVncmF0aW9uX21ldGhvZCwgIl9VTUFQIikpICU+JQogICAgICB0aWJibGU6OmFzX3RpYmJsZShyb3duYW1lcyA9ICJjZWxsX25hbWUiKSAlPiUKICAgICAgZHBseXI6OnJlbmFtZShVTUFQMSA9IFYxLCBVTUFQMiA9IFYyKQogICkgJT4lCiAgZHBseXI6OnNlbGVjdCgtY2VsbF9iYXJjb2RlKSAlPiUKICAjIHJlbWFrZSBgY2VsbHR5cGVfY29sbGFwc2VkYCBzbyB0aGF0IGl0J3MgdGhlIHRvcCBwYXJhbXMkbWF4X2NlbGx0eXBlcyAKICAjICBjb25zaWRlcmluZyBhbGwgbGlicmFyaWVzIHBvb2xlZAogIGRwbHlyOjptdXRhdGUoY2VsbHR5cGVfY29sbGFwc2VkID0gZm9yY2F0czo6ZmN0X2x1bXAoY2VsbHR5cGUsIHBhcmFtcyRtYXhfY2VsbHR5cGVzKSkKCiMgV2UgY2FuIG5vdyByZW1vdmUgdGhlIGxhcmdlIGBzY2VfbGlzdF9hbm5vdGF0ZWRgIGFuZCBgaW50ZWdyYXRlZF9zY2VgCnJtKHNjZV9saXN0X2Fubm90YXRlZCkKcm0oaW50ZWdyYXRlZF9zY2UpCgojIFNlZSBkZjoKY2VsbHR5cGVfYWR0X2RmCmBgYAoKCmBgYHtyfQojIFJOQSBVTUFQOgphbGxfY2VsbHR5cGVzX3JuYV91bWFwIDwtIGNlbGx0eXBlX2FkdF9kZiAlPiUKICBkcGx5cjo6c2VsZWN0KFVNQVAxLCBVTUFQMiwgY2VsbHR5cGUpICU+JQogIGRwbHlyOjpkaXN0aW5jdCgpICU+JQogIGdncGxvdCgpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2VsbHR5cGUpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMC4zLCBhbHBoYSA9IDAuNCkgKyAKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplPTMpKSkgKwogIGdndGl0bGUoZ2x1ZTo6Z2x1ZSgiSW50ZWdyYXRlZCBVTUFQIHdpdGggYWxsIFNpbmdsZVItYW5ub3RhdGVkIGNlbGx0eXBlcyBzaG93biIpKQoKYWxsX2NlbGx0eXBlc19ybmFfdW1hcApgYGAKYGBge3J9CmFsbF9jZWxsdHlwZXNfcm5hX3VtYXAgKwogIGxpbXMoCiAgICB4ID0gYygtNiwgNCksIAogICAgeSA9IGMoLTUsIDUuNSkKICApICsKICBnZ3RpdGxlKCJUaGUgYWJvdmUgVU1BUCwgem9vbWVkIGluIikKYGBgCgoKYGBge3J9CiMgUk5BIFVNQVA6Cm1heF9jZWxsdHlwZXNfcm5hX3VtYXAgPC0gY2VsbHR5cGVfYWR0X2RmICU+JQogIGRwbHlyOjpzZWxlY3QoVU1BUDEsIFVNQVAyLCBjZWxsdHlwZV9jb2xsYXBzZWQpICU+JQogIGRwbHlyOjpkaXN0aW5jdCgpICU+JQogIGdncGxvdCgpICsKICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2VsbHR5cGVfY29sbGFwc2VkKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IDAuMywgYWxwaGEgPSAwLjQpICsgCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZT0zKSkpICsKICBsYWJzKAogICAgdGl0bGUgPSBnbHVlOjpnbHVlKCJJbnRlZ3JhdGVkIFVNQVAgd2l0aCB0b3Age3BhcmFtcyRtYXhfY2VsbHR5cGVzfSBTaW5nbGVSLWFubm90YXRlZCBjZWxsdHlwZXMgc2hvd24iKSwgCiAgICBjb2xvciA9IGdsdWU6OmdsdWUoIlRvcCB7cGFyYW1zJG1heF9jZWxsdHlwZXN9IGNlbGx0eXBlcyIpKQoKbWF4X2NlbGx0eXBlc19ybmFfdW1hcApgYGAKCmBgYHtyfQptYXhfY2VsbHR5cGVzX3JuYV91bWFwICsKICBsaW1zKAogICAgeCA9IGMoLTYsIDQpLCAKICAgIHkgPSBjKC01LCA1LjUpCiAgKSArCiAgZ2d0aXRsZSgiVGhlIGFib3ZlIFVNQVAsIHpvb21lZCBpbiIpCmBgYAoKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIG1ha2UgVU1BUHMgY29sb3JlZCBieSBBRFQKbWFrZV9hZHRfdW1hcCA8LSBmdW5jdGlvbihjZWxsdHlwZV9hZHRfZGYsIGFkdF9uYW1lKSB7CgogIGNlbGx0eXBlX2FkdF9kZiAlPiUKICAgIGRwbHlyOjpmaWx0ZXIoQURUID09IGFkdF9uYW1lKSAlPiUKICAgIGdncGxvdCgpICsgCiAgICBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gbG9nY291bnRzKSArIAogICAgZ2VvbV9wb2ludChzaXplID0gMC4zLCBhbHBoYSA9IDAuNCkgKwogICAgc2NhbGVfY29sb3JfdmlyaWRpc19jKCkgKyAKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2FkdF9uYW1lfSBleHByZXNzaW9uIiksCiAgICAgIGNvbG9yID0gIk5vcm1hbGl6ZWQgZXhwcmVzc2lvbiIpCn0KCm1ha2VfYWR0X3VtYXAoY2VsbHR5cGVfYWR0X2RmLCAiQ0QzIikgIyBib3R0b20gbGVmdCBjb3JuZXIgdGlueSBkb3RzIQptYWtlX2FkdF91bWFwKGNlbGx0eXBlX2FkdF9kZiwgIkNEMTIzIikgIyBldmVyeXRoaW5nIGlzIHR1bW9yCm1ha2VfYWR0X3VtYXAoY2VsbHR5cGVfYWR0X2RmLCAiQ0Q0NSIpICMgc2hvdWxkIGJlIG9uIGFsbCBkaWZmZXJlbnRpYXRlZCBjZWxscyBtb3JlIG9yIGxlc3NcCm1ha2VfYWR0X3VtYXAoY2VsbHR5cGVfYWR0X2RmLCAiQ0QxOSIpICMgYiBjZWxscwoKYGBgCgojIyBBRFQgZGlzdHJpYnV0aW9ucwoKVGhpcyBzZWN0aW9uIGV4cGxvcmVzIEFEVCBjb3VudHMgYWNyb3NzIHByZWRpY3RlZCBjZWxsdHlwZXMuCgoKYGBge3IgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9Nn0KYWR0X2NlbGx0eXBlX3Zpb2xpbiA8LSBmdW5jdGlvbihkZiwgY2VsbHR5cGVfY29sdW1uLCBhZHRfbmFtZSkgewogIAogICMgY29sb3JzLCB1c2luZyBkcGx5ciBzaW5jZSB0aWR5ZXZhbCBsYW5kCiAgZmlsbF92ZWN0b3IgPC0gZGYgJT4lCiAgICBkcGx5cjo6cHVsbCh7e2NlbGx0eXBlX2NvbHVtbn19KSAlPiUKICAgIHVuaXF1ZSgpICU+JQogICAgbGVuZ3RoKCkgJT4lCiAgICByYWluYm93KCkKCiAgY2VsbHR5cGVfYWR0X2RmICU+JQogICAgZHBseXI6OmZpbHRlcihBRFQgPT0gYWR0X25hbWUpICU+JQogICAgZHBseXI6Om11dGF0ZShjZWxsdHlwZV9wbG90X2NvbHVtbiA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKHt7Y2VsbHR5cGVfY29sdW1ufX0sIGxvZ2NvdW50cykpICU+JQogICAgZ2dwbG90KCkgKyAKICAgIGFlcyh4ID0gY2VsbHR5cGVfcGxvdF9jb2x1bW4sCiAgICAgICAgeSA9IGxvZ2NvdW50cywgCiAgICAgICAgZmlsbCA9IGNlbGx0eXBlX3Bsb3RfY29sdW1uKSArIAogICAgZ2VvbV92aW9saW4oYWxwaGEgPSAwLjcpICsgCiAgICBzdGF0X3N1bW1hcnkoKSArIAogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gZmlsbF92ZWN0b3IpICsKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gZ2x1ZTo6Z2x1ZSgie2FkdF9uYW1lfSBub3JtYWxpemVkIGV4cHJlc3Npb24gYWNyb3NzIGNlbGx0eXBlcyIpLCAKICAgICAgeCA9ICJDZWxsdHlwZXMiLCAKICAgICAgeSA9ICJOb3JtYWxpemVkIGV4cHJlc3Npb24iCiAgICApICsgCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IHJlbCgwLjgpKSkKCn0KCiMjIENEMwphZHRfY2VsbHR5cGVfdmlvbGluKGNlbGx0eXBlX2FkdF9kZiwgY2VsbHR5cGUsICJDRDMiKSAKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlX2NvbGxhcHNlZCwgIkNEMyIpCgojIyBDRDE5CmFkdF9jZWxsdHlwZV92aW9saW4oY2VsbHR5cGVfYWR0X2RmLCBjZWxsdHlwZSwgIkNEMTkiKSAKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlX2NvbGxhcHNlZCwgIkNEMTkiKQoKIyMgQ0QxMjMKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlLCAiQ0QxMjMiKSAKYWR0X2NlbGx0eXBlX3Zpb2xpbihjZWxsdHlwZV9hZHRfZGYsIGNlbGx0eXBlX2NvbGxhcHNlZCwgIkNEMTIzIikKYGBgCgoKIyMgQ29tcGFyaW5nIHJlZmVyZW5jZXMKClRvIGJ1aWxkIGEgc2Vuc2Ugb2YgaG93IGRpZmZlcmVudCByZWZlcmVuY2VzIHBlcmZvcm0sIGxldCdzIGp1c3QgbG9vayBhdCBvbmUgU0NFIChhcmJpdHJhcmlseSBjaG9zZW4gYXMgdGhlIGZpcnN0KToKCmBgYHtyfQpzY2UgPC0gcmVhZHI6OnJlYWRfcmRzKHNjZV9maWxlX3BhdGhzW1sxXV0pCmBgYAoKCkZ1bmN0aW9ucyBmb3IgY29tcGFyaW5nIHJlZmVyZW5jZXM6CmBgYHtyfQojIFJldHVybiBhIHRpYmJsZSBvZiBwcmVkaWN0aW9ucyBmcm9tIHRoZSB0d28gcmVmZXJlbmNlcwoKIyBEZWZpbmUgb3V0c2lkZSBvZiBmdW5jdGlvbiB0byBhdm9pZCByZXJ1bm5pbmcgb3ZlciBhbmQgb3ZlciBhbmQgb3ZlciBBTkQgT1ZFUi4KaHBjYV9yZWYgPC0gY2VsbGRleDo6SHVtYW5QcmltYXJ5Q2VsbEF0bGFzRGF0YShlbnNlbWJsID0gVFJVRSkKYmx1ZXByaW50X2VuY29kZV9yZWYgPC0gY2VsbGRleDo6Qmx1ZXByaW50RW5jb2RlRGF0YShlbnNlbWJsID0gVFJVRSkKCmNvbXBhcmVfcHJlZGljdGlvbnMgPC0gZnVuY3Rpb24oc2NlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9uYW1lKSB7CiAgCiAgIyBIUENBIHByZWRpY3Rpb24KICBwcmVkc19ocGNhIDwtIFNpbmdsZVI6OlNpbmdsZVIoCiAgICB0ZXN0ID0gc2NlLCAKICAgIHJlZiA9IGhwY2FfcmVmLAogICAgbGFiZWxzID0gY29sRGF0YShocGNhX3JlZilbLGxhYmVsX25hbWVdLCAKICAgIEJQUEFSQU0gPSBCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKHBhcmFtcyRjb3JlcykKICApCiAgCiAgIyBCbHVlcHJpbnQvRU5DT0RFIHByZWRpY3Rpb24KICBwcmVkc19iZSA8LSBTaW5nbGVSOjpTaW5nbGVSKAogICAgdGVzdCA9IHNjZSwgCiAgICByZWYgPSBibHVlcHJpbnRfZW5jb2RlX3JlZiwKICAgIGxhYmVscyA9IGNvbERhdGEoYmx1ZXByaW50X2VuY29kZV9yZWYpWyxsYWJlbF9uYW1lXSwgCiAgICBCUFBBUkFNID0gQmlvY1BhcmFsbGVsOjpNdWx0aWNvcmVQYXJhbShwYXJhbXMkY29yZXMpCiAgKQogIAogICMgQ29tYmluZSByZXN1bHRzIGludG8gYSB3aWRlIHRpYmJsZToKICBwcmVkc19kZiA8LSB0aWJibGU6OnRpYmJsZSgKICAgIGhwY2EgPSBwcmVkc19ocGNhJGxhYmVscywgCiAgICBjZWxsX2JhcmNvZGUgPSByb3duYW1lcyhwcmVkc19ocGNhKSkgJT4lCiAgICBkcGx5cjo6aW5uZXJfam9pbigKICAgICAgdGliYmxlOjp0aWJibGUoCiAgICAgICAgYmx1ZXByaW50X2VuY29kZSA9IHByZWRzX2JlJGxhYmVscywgCiAgICAgICAgY2VsbF9iYXJjb2RlID0gcm93bmFtZXMocHJlZHNfYmUpKSAKICAgICkKCiAgcmV0dXJuKHByZWRzX2RmKQp9CmBgYAoKVGhlIFtgY2VsbGRleGBdKChodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9kYXRhL2V4cGVyaW1lbnQvdmlnbmV0dGVzL2NlbGxkZXgvaW5zdC9kb2MvdXNlcmd1aWRlLmh0bWwpKSBwYWNrYWdlIGNvbnRhaW5zIGJ1bGsgUk5BLVNlcSBkYXRhc2V0cyBmb3IgdXNlIGFzIHJlZmVyZW5jZS4KVGhlc2UgdHdvIGFyZSBsaWtlbHkgbW9zdCB1c2VmdWw6Cgo+IFRoZSBIUENBIHJlZmVyZW5jZSBjb25zaXN0cyBvZiBwdWJsaWNseSBhdmFpbGFibGUgbWljcm9hcnJheSBkYXRhc2V0cyBkZXJpdmVkIGZyb20gaHVtYW4gcHJpbWFyeSBjZWxscyAoTWFiYm90dCBldCBhbC4gMjAxMykuIE1vc3Qgb2YgdGhlIGxhYmVscyByZWZlciB0byBibG9vZCBzdWJwb3B1bGF0aW9ucyBidXQgY2VsbCB0eXBlcyBmcm9tIG90aGVyIHRpc3N1ZXMgYXJlIGFsc28gYXZhaWxhYmxlLgoKCj4gVGhlIEJsdWVwcmludC9FTkNPREUgcmVmZXJlbmNlIGNvbnNpc3RzIG9mIGJ1bGsgUk5BLXNlcSBkYXRhIGZvciBwdXJlIHN0cm9tYSBhbmQgaW1tdW5lIGNlbGxzIGdlbmVyYXRlZCBieSBCbHVlcHJpbnQgKE1hcnRlbnMgYW5kIFN0dW5uZW5iZXJnIDIwMTMpIGFuZCBFTkNPREUgcHJvamVjdHMgKFRoZSBFTkNPREUgUHJvamVjdCBDb25zb3J0aXVtIDIwMTIpLgoKCgpgYGB7cn0KbWFpbl9sYWJlbHNfZGYgPC0gY29tcGFyZV9wcmVkaWN0aW9ucyhzY2UsICJsYWJlbC5tYWluIikKZmluZV9sYWJlbHNfZGYgPC0gY29tcGFyZV9wcmVkaWN0aW9ucyhzY2UsICJsYWJlbC5maW5lIikKCiMgcmVtb3ZlIHJlZnMgd2hpY2ggYXJlIGxhcmdlCnJtKGhwY2FfcmVmKQpybShibHVlcHJpbnRfZW5jb2RlX3JlZikKCmBgYAoKSG93IGRvIHRoZXNlIHByZWRpY3Rpb25zIGNvbXBhcmU/Ck5vdGUgdGhhdCB0aGVzZSByZWZlcmVuY2VzIG9mdGVuIHVzZSBkaWZmZXJlbnQgbGFiZWxzIGZvciB0aGUgc2FtZSBjZWxsIHR5cGUgc28gd2Ugc2hvdWxkbid0IGV4cGVjdCBhIHBlcmZlY3QgZGlhZ29uYWwgYmVsb3c6CgpgYGB7cn0KI2Z1bmN0aW9uIGZvciBtYWtpbmcgY29tcGFyaXNvbiBoZWF0bWFwcwptYWtlX2NvbXBhcmlzb25faGVhdG1hcCA8LSBmdW5jdGlvbihkZikgewogIGRmICU+JQogICAgZHBseXI6Om11dGF0ZSgKICAgICAgaHBjYSA9IGZvcmNhdHM6OmZjdF9yZXYoZm9yY2F0czo6ZmN0X2luZnJlcShocGNhKSksCiAgICAgIGJsdWVwcmludF9lbmNvZGUgPSBmb3JjYXRzOjpmY3RfcmV2KGZvcmNhdHM6OmZjdF9pbmZyZXEoYmx1ZXByaW50X2VuY29kZSkpCiAgICApICU+JQogICAgZHBseXI6OmNvdW50KGhwY2EsIGJsdWVwcmludF9lbmNvZGUpICU+JQogIGdncGxvdCgpICsgCiAgYWVzKHggPSBibHVlcHJpbnRfZW5jb2RlLCB5ID0gaHBjYSwgZmlsbCA9IG4pICsgCiAgZ2VvbV90aWxlKGNvbG9yID0gImJsYWNrIikgKyAKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiksIHNpemUgPSByZWwoMS4yKSkgKyAKICBzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlJkWWxCdSIsIG5hbWUgPSAiTnVtLiBjZWxscyIpICsgCiAgIyBiYWNrZ3JvdW5kIGdyaWQgZnJvbSB0aGVtZV9idyBkb2VzIG5vdCBtYXRjaCB1cCB3aXRoIHJlY3RhbmdsZXMuCiAgdGhlbWVfY2xhc3NpYygpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDMwLCBoanVzdCA9IDEpKQp9CmBgYAoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0KbWFrZV9jb21wYXJpc29uX2hlYXRtYXAobWFpbl9sYWJlbHNfZGYpICsgZ2d0aXRsZSgiQnJvYWQgbGFiZWwgY29tcGFyaXNvbiIpCmBgYAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTB9Cm1ha2VfY29tcGFyaXNvbl9oZWF0bWFwKGZpbmVfbGFiZWxzX2RmKSArIGdndGl0bGUoIkZpbmUgbGFiZWwgY29tcGFyaXNvbiIpCmBgYAoKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uX2luZm99CnNlc3Npb25JbmZvKCkKYGBgCg==